﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;


namespace EnumUtils
{
  public static class EnumFlagsUtilities
  {
    public static BitArray ConvertEnumToBitArray( Enum enumerationFlagsToProcess ) {

      Type enumFlagsUnderlyingType = Enum.GetUnderlyingType(enumerationFlagsToProcess.GetType());
      dynamic enumFlagsBits_integer = Convert.ChangeType(enumerationFlagsToProcess, enumFlagsUnderlyingType);
      BitArray enumFlagsBits = new BitArray(BitConverter.GetBytes(enumFlagsBits_integer));

      return enumFlagsBits;
    }

    public static Enum 
    ConvertIntToEnum( Type typeofEnum, int enumAsAnInt ) {
      return (Enum)Enum.Parse(typeofEnum, enumAsAnInt.ToString());
    }



    [Pure]
    public static bool
    DoesEnumHaveFlagsAttribute( Enum enumerationToTest ) {
      bool hasAFlagsAttribute = enumerationToTest.GetType().GetCustomAttributes(false).Any(flag => flag.GetType() == typeof(FlagsAttribute));
      return hasAFlagsAttribute;
    }


    /// <summary>
    /// Performs an operation on each flag of a flags enumeration.
    /// </summary>
    /// <param name="enumerationFlagsToProcess">An enumeration with a Flags attribute to process.</param>
    /// <param name="operation">An operation to execute on the passed enumeration. Must not attempt to do operations that you can't do to an enumeration (like bit-shifting).</param>
    public static void
    ForeachEnumFlag( Enum enumerationFlagsToProcess, Action<Enum> operation ) {
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(enumerationFlagsToProcess), ErrorMessageUtils.EnumNotAFlagsEnumMessage);
      Contract.Requires<ArgumentNullException>(operation != default(Action<Enum>));

      BitArray enumFlagsBits = ConvertEnumToBitArray(enumerationFlagsToProcess);

      for (int i = enumFlagsBits.Count - 1; i >= 0; --i) {
        ulong enumAsInteger = Convert.ToUInt64(enumFlagsBits[i]) << i;
        Enum individualEnumFlag = (Enum)Enum.Parse(enumerationFlagsToProcess.GetType(), enumAsInteger.ToString());
        if (IsFlagDefined(individualEnumFlag)) {
          operation(individualEnumFlag);
        }
      }
    }



    /// <summary>
    /// Tests to determine if an enumeration flag is explicitly defined for an enumeration.
    /// </summary>
    /// <param name="flagToTest"></param>
    /// <returns></returns>
    /// <remarks>
    /// Code based off of a code snippet found on pp. 95 in "C# 3.0 in a 
    /// Nutshell", written by Albahari, J &amp; Albahari, B.
    /// </remarks>
    public static bool
    IsFlagDefined( Enum flagToTest ) {
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(flagToTest), ErrorMessageUtils.EnumNotAFlagsEnumMessage);

      if (flagToTest.ToString() == "0") {
        return false;
      }
      else {
        return true;
      }
    }


    /// <summary>
    /// Gets an enumerator for a flags enumeration that enumerates through all flag values.
    /// </summary>
    /// <param name="enumFlags"></param>
    /// <returns></returns>
    public static IEnumerator
    GetEnumerator( Enum enumFlags ) {
      BitArray enumBits = ConvertEnumToBitArray(enumFlags);

      return enumBits.GetEnumerator();
    }


    /// <summary>
    /// Gets an enumerator for a flags enumeration that enumerates through all flag values.
    /// </summary>
    /// <param name="enumFlags"></param>
    /// <returns></returns>
    public static IEnumerator<Enum>
    GetGenericEnumerator( Enum enumFlags ) {
      Contract.Requires<ArgumentException>(DoesEnumHaveFlagsAttribute(enumFlags), ErrorMessageUtils.EnumNotAFlagsEnumMessage);


      BitArray enumBits = ConvertEnumToBitArray(enumFlags);

      foreach (bool flag in enumBits) {
        Enum enumFlag = (Enum)Enum.Parse(enumFlags.GetType(), Convert.ToInt32(flag).ToString());
        if (IsFlagDefined(enumFlag)) {
          yield return enumFlag;
        }
      }

    }



    #region Helper Methods
    #endregion
  }
}
